/*
 * Copyright (c) 2023-2024 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.spring.authorizationserver.configuration;

import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elsfs.cloud.common.properties.ElsfsSecurityProperties;
import org.elsfs.cloud.common.security.configurer.AbstractServletSecurityCustomConfiguration;
import org.elsfs.cloud.spring.authorizationserver.component.RsaKeyPairRepositoryOAuth2TokenCustomizer;
import org.elsfs.cloud.spring.authorizationserver.oidc.OidcUserInfoMapper;
import org.elsfs.cloud.spring.authorizationserver.oidc.UserInfoResponseHandler;
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
 * SecurityFilterChain 配置
 *
 * @author zeng
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableConfigurationProperties({
  ElsfsSecurityProperties.class,
  OAuth2AuthorizationServerProperties.class
})
@Slf4j
public class AuthorizationServerAutoConfiguration
    extends AbstractServletSecurityCustomConfiguration {
  private final ElsfsSecurityProperties elsfsSecurityProperties;
  private final JwtEncoder jwtEncoder;
  private final RsaKeyPairRepositoryOAuth2TokenCustomizer rsaKeyPairRepositoryOAuth2TokenCustomizer;

  @Bean
  @Order(1)
  SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    LOGGER.info("oauth 配置开始");
    OAuth2AuthorizationServerConfigurer authz = getoAuth2AuthorizationServerConfigurer();
    RequestMatcher endpointsMatcher = authz.getEndpointsMatcher();
    http.securityMatcher(endpointsMatcher);
    // 配置需要认证的请求
    addHttpSecurityAuthorizeHttpRequests(http);
    // 配置csrf
    http.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher));
    // 配置异常处理
    http.exceptionHandling(
        e -> e.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));
    // 开启jwt
    http.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
    http.with(authz, Customizer.withDefaults());
    var securityFilterChain = http.build();
    LOGGER.info("oauth 配置结束");
    return securityFilterChain;
  }

  private OAuth2AuthorizationServerConfigurer getoAuth2AuthorizationServerConfigurer() {
    OAuth2AuthorizationServerConfigurer authz = new OAuth2AuthorizationServerConfigurer();
    UserInfoResponseHandler userInfoResponseHandler = new UserInfoResponseHandler();
    //  配置授权页面
    authz.authorizationEndpoint(e -> e.consentPage(elsfsSecurityProperties.getConsentPage()));
    authz.oidc(
        oidc ->
            oidc.userInfoEndpoint(
                e -> {
                  e.userInfoRequestConverter(getAuthenticationConverter());
                  // AuthenticationSuccessHandler（后处理器），用于处理 "已认证" 的
                  // OidcUserInfoAuthenticationToken 并返回 UserInfo
                  // 响应
                  e.userInfoResponseHandler(userInfoResponseHandler);
                  // 失败处理
                  e.errorResponseHandler(userInfoResponseHandler);
                  e.userInfoMapper(new OidcUserInfoMapper());
                }));
    return authz;
  }

  /**
   * token生成器
   *
   * @return token生成器
   */
  @Bean
  OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator() {
    JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
    jwtGenerator.setJwtCustomizer(rsaKeyPairRepositoryOAuth2TokenCustomizer);
    OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
    OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
    return new DelegatingOAuth2TokenGenerator(
        jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
  }

  /**
   * 添加一个 AuthenticationConverter（预处理器），当试图从 HttpServletRequest 中提取 UserInfo 请求到
   * OidcUserInfoAuthenticationToken 的一个实例时使用
   *
   * @return AuthenticationConverter
   */
  private AuthenticationConverter getAuthenticationConverter() {
    return new DelegatingAuthenticationConverter(
        List.of(
            (request) -> {
              Authentication authentication =
                  SecurityContextHolder.getContext().getAuthentication();
              // authentication JwtAuthenticationToken
              return new OidcUserInfoAuthenticationToken(authentication);
            }));
  }

  @Override
  public void afterPropertiesSet() throws Exception {}
}
